module Syspec

  class Task

    attr_accessor :reporter, :domain, :conditions, :current
    attr_accessor :results, :parameters

    def initialize(domain, &block)
      self.reporter = Reporter.new
      report :new_task do
        self.domain = domain.to_s
      end
      if report(:domain_exists) { domain_exists? }
        report(:header) { nil }
        initialize_conditions
        initialize_parameters
        block_given? && instance_eval(&block)
      end
    end

    def initialize_conditions
      self.conditions = {}
    end
    
    def initialize_parameters
      self.parameters = {}
    end

    def initialize_current 
      self.current = OpenStruct.new
    end
    
    def initialize_results
      self.results = []
    end

    def report(key, options = {}, &block)
      reporter.notify(key, options, &block)
    end
    
    def domain_exists?
      begin
        Net::HTTP.start(domain, 80)
        true
      rescue
        false
      end
    end

    def fetch(request_method, url)
      report :request do
        initialize_current unless current
        current.request_method = request_method
        current.uri = URI.parse("http://#{domain}#{url}")
      end
      report :response, :timer => true do
        current.request = create_request
        authenticate
        current.response = send_request
      end
      report :passed? do
        check_conditions
        passed?
      end
      report :individual_results do
        results
      end
    end
    
    def passed?
      conditions.empty? ? true : results.all?(&:last)
    end

    def failed?
      !passed?
    end
    
    def check_conditions
      @results = conditions.inject([]) do |results, (key, condition)|
        actual = current.response.send(key)
        results << [key, condition, condition.compare(actual)]; results
      end
    end

    def update_conditions(key, operation, expected)
      conditions[key] = Condition.new(operation, expected)
    end
    
    def authenticate
      current.request.basic_auth *parameters[:login] if parameters[:login]
    end

    # :begin: DSL

    def login(account, password)
      parameters[:login] = [account, password]
    end
    
    def no_login
      parameters[:login] = nil
    end

    def accept(*codes)
      if codes.size == 1
        update_conditions(:code, :equals, codes[0].to_s)
      else
        update_conditions(:code, :includes, codes.map(&:to_s))
      end
    end
    
    # example: respond_within 0.1, 0.03
    # checks response time is within 0.3 plus or minus of the base
    # warning: this is very much dependent on your own connection latency!
    def respond_within(base, delta)
      update_conditions(:time, :delta, [base, delta])
    end
    
    def contain(text)
      if text.is_a? Regexp
        update_conditions(:body, :match, text)
      else
        update_conditions(:body, :equals, text)
      end
    end

    [:get, :post, :delete, :put].each do |m|
      define_method(m) { |url| fetch m, url }
    end


    # :end: DSL

    private

    def create_request
      request_class.new(current.uri.path)
    end
    
    def send_request
      begin
        Net::HTTP.start(current.uri.host, current.uri.port) do |http|
          http.request(current.request)
        end
      rescue SocketError => e
        create_response(-1, e.message)
      end
    end
    
    def create_response(code, body, extras = {})
      OpenStruct.new({ :code => code, :body => body }.merge(extras))
    end

    def request_class
      Net::HTTP.const_get(current.request_method.to_s.capitalize)
    end

  end
end